Animation float 优化
Unity Animation 默认float 精度很长,在处理这些clip时,实际我们使用时,用不到这么高的精度,需要对这个部分进行裁剪
在我自己项目中,所有 Animation Clip 文件裁剪前是 17M左右,裁剪后是5.4M左右,对内存的提升还是很明显的
下面代码直接放在Edtior中即可
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.Profiling;
namespace ETEditor {
class AnimationOpt {
static Dictionary<uint, string> _FLOAT_FORMAT;
static MethodInfo getAnimationClipStats;
static FieldInfo sizeInfo;
static object[] _param = new object[1];
static AnimationOpt() {
_FLOAT_FORMAT = new Dictionary<uint, string>();
for(uint i = 1; i < 6; i++){ _FLOAT_FORMAT.Add(i, "f" + i.ToString()); }
Assembly asm = Assembly.GetAssembly(typeof(Editor));
getAnimationClipStats = typeof(AnimationUtility).GetMethod(
"GetAnimationClipStats",
BindingFlags.Static | BindingFlags.NonPublic
);
Type aniclipstats = asm.GetType("UnityEditor.AnimationClipStats");
sizeInfo = aniclipstats.GetField("size", BindingFlags.Public | BindingFlags.Instance);
}
AnimationClip _clip;
string _path;
public string path { get { return _path; } }
public long originFileSize { get; private set; }
public int originMemorySize { get; private set; }
public int originInspectorSize { get; private set; }
public long optFileSize { get; private set; }
public int optMemorySize { get; private set; }
public int optInspectorSize { get; private set; }
public AnimationOpt(string path, AnimationClip clip) {
_path = path;
_clip = clip;
_GetOriginSize();
}
void _GetOriginSize() {
originFileSize = _GetFileZie();
originMemorySize = _GetMemSize();
originInspectorSize = _GetInspectorSize();
}
void _GetOptSize() {
optFileSize = _GetFileZie();
optMemorySize = _GetMemSize();
optInspectorSize = _GetInspectorSize();
}
long _GetFileZie() {
FileInfo fi = new FileInfo(_path);
return fi.Length;
}
int _GetMemSize() { return Profiler.GetRuntimeMemorySize(_clip); }
int _GetInspectorSize() {
_param[0] = _clip;
var stats = getAnimationClipStats.Invoke(null, _param);
return(int) sizeInfo.GetValue(stats);
}
void _OptmizeAnimationScaleCurve() {
if(_clip != null){
//去除scale曲线
foreach(EditorCurveBinding theCurveBinding in AnimationUtility.GetCurveBindings(_clip)){
string name = theCurveBinding.propertyName.ToLower();
if(name.Contains("scale")){
AnimationUtility.SetEditorCurve(_clip, theCurveBinding, null);
Debug.LogFormat("关闭{0}的scale curve", _clip.name);
}
}
}
}
void _OptmizeAnimationFloat_X(uint x) {
if(_clip != null && x > 0){
//浮点数精度压缩到f3
AnimationClipCurveData[] curves = null;
curves = AnimationUtility.GetAllCurves(_clip);
Keyframe key;
Keyframe[] keyFrames;
string floatFormat;
if(_FLOAT_FORMAT.TryGetValue(x, out floatFormat)){
if(curves != null && curves.Length > 0){
for(int ii = 0; ii < curves.Length; ++ii){
AnimationClipCurveData curveDate = curves[ii];
if(curveDate.curve == null || curveDate.curve.keys == null){
//Debug.LogWarning(string.Format("AnimationClipCurveData {0} don't have curve; Animation name {1} ", curveDate, animationPath));
continue;
}
keyFrames = curveDate.curve.keys;
for(int i = 0; i < keyFrames.Length; i++){
key = keyFrames[i];
key.value = float.Parse(key.value.ToString(floatFormat));
key.inTangent = float.Parse(key.inTangent.ToString(floatFormat));
key.outTangent = float.Parse(key.outTangent.ToString(floatFormat));
key.inWeight = float.Parse(key.inWeight.ToString(floatFormat));
key.outWeight = float.Parse(key.outWeight.ToString(floatFormat));
keyFrames[i] = key;
}
curveDate.curve.keys = keyFrames;
_clip.SetCurve(
curveDate.path,
curveDate.type,
curveDate.propertyName,
curveDate.curve
);
}
}
}
else{ Debug.LogErrorFormat("目前不支持{0}位浮点", x); }
}
}
public void Optimize(bool scaleOpt, uint floatSize) {
if(scaleOpt){ _OptmizeAnimationScaleCurve(); }
_OptmizeAnimationFloat_X(floatSize);
_GetOptSize();
}
public void Optimize_Scale_Float3() { Optimize(false, 3); }
public void LogOrigin() { _logSize(originFileSize, originMemorySize, originInspectorSize); }
public void LogOpt() { _logSize(optFileSize, optMemorySize, optInspectorSize); }
public void LogDelta() { }
void _logSize(long fileSize, int memSize, int inspectorSize) {
Debug.LogFormat(
"{0} \nSize=[ {1} ]",
_path,
string.Format(
"FSize={0} ; Mem->{1} ; inspector->{2}",
EditorUtility.FormatBytes(fileSize),
EditorUtility.FormatBytes(memSize),
EditorUtility.FormatBytes(inspectorSize)
)
);
}
}
public class OptimizeAnimationClipTool {
static List<AnimationOpt> _AnimOptList = new List<AnimationOpt>();
static List<string> _Errors = new List<string>();
static int _Index = 0;
[MenuItem("Assets/Animation/裁剪浮点数去除Scale")]
public static void Optimize() {
_AnimOptList = FindAnims();
if(_AnimOptList.Count > 0){
_Index = 0;
_Errors.Clear();
EditorApplication.update = ScanAnimationClip;
}
}
private static void ScanAnimationClip() {
AnimationOpt _AnimOpt = _AnimOptList[_Index];
bool isCancel = EditorUtility.DisplayCancelableProgressBar(
"优化AnimationClip",
_AnimOpt.path,
(float) _Index / (float) _AnimOptList.Count
);
_AnimOpt.Optimize_Scale_Float3();
_Index++;
if(isCancel || _Index >= _AnimOptList.Count){
EditorUtility.ClearProgressBar();
Debug.Log(
string.Format(
"--优化完成-- 错误数量: {0} 总数量: {1}/{2} 错误信息↓:\n{3}\n----------输出完毕----------",
_Errors.Count,
_Index,
_AnimOptList.Count,
string.Join(string.Empty, _Errors.ToArray())
)
);
Resources.UnloadUnusedAssets();
GC.Collect();
AssetDatabase.SaveAssets();
EditorApplication.update = null;
_AnimOptList.Clear();
_cachedOpts.Clear();
_Index = 0;
}
}
static Dictionary<string, AnimationOpt> _cachedOpts = new Dictionary<string, AnimationOpt>();
static AnimationOpt _GetNewAOpt(string path) {
AnimationOpt opt = null;
if(!_cachedOpts.ContainsKey(path)){
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
if(clip != null){
opt = new AnimationOpt(path, clip);
_cachedOpts[path] = opt;
}
}
return opt;
}
static List<AnimationOpt> FindAnims() {
string[] guids = null;
List<string> path = new List<string>();
List<AnimationOpt> assets = new List<AnimationOpt>();
UnityEngine.Object[] objs = Selection.GetFiltered(typeof(object), SelectionMode.Assets);
if(objs.Length > 0){
for(int i = 0; i < objs.Length; i++){
if(objs[i].GetType() == typeof(AnimationClip)){
string p = AssetDatabase.GetAssetPath(objs[i]);
AnimationOpt animopt = _GetNewAOpt(p);
if(animopt != null) assets.Add(animopt);
}
else
path.Add(AssetDatabase.GetAssetPath(objs[i]));
}
if(path.Count > 0)
guids = AssetDatabase.FindAssets(
string.Format("t:{0}", typeof(AnimationClip).ToString().Replace("UnityEngine.", "")),
path.ToArray()
);
else
guids = new string[] { };
}
for(int i = 0; i < guids.Length; i++){
string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
AnimationOpt animopt = _GetNewAOpt(assetPath);
if(animopt != null) assets.Add(animopt);
}
return assets;
}
}
}